
#pragma once

#include <vector>
#include <string>
#include <map>
#include <functional>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/glm.hpp>

#include "Frustum.h"
#include "pch.h"


namespace Geometry
{
    using namespace glm;
    using namespace Math;
    
    template<class VertexType , class IndexType = unsigned long>
    struct MeshData
    {
        std::vector<VertexType> vertexVec;	
        std::vector<IndexType> indexVec;	

        MeshData()
        {
            static_assert(sizeof(IndexType) == 2 || sizeof(IndexType) == 4, "The size of IndexType must be 2 bytes or 4 bytes!");
            static_assert(std::is_unsigned<IndexType>::value, "IndexType must be unsigned integer!");
        }
    };
    template<class VertexType , class IndexType = unsigned long>
    MeshData<VertexType, IndexType> CreateSphere(float radius = 1.0f, unsigned int levels = 20, unsigned int slices = 20,
        const glm::vec4& color = { 1.0f, 1.0f, 1.0f, 1.0f });

    template<class VertexType , class IndexType = unsigned long>
    MeshData<VertexType, IndexType> CreateBox(float width = 2.0f, float height = 2.0f, float depth = 2.0f,
        const glm::vec4& color = { 1.0f, 1.0f, 1.0f, 1.0f });
    
    
    template<class VertexType , class IndexType = unsigned long>
    MeshData<VertexType, IndexType> CreateBounding(float width = 2.0f, float height = 2.0f, float depth = 2.0f,
        const glm::vec4& color = { 1.0f, 1.0f, 1.0f, 1.0f });

    template<class VertexType , class IndexType = unsigned long>
    MeshData<VertexType, IndexType> CreateCylinder(float radius = 1.0f, float height = 2.0f, unsigned int slices = 20, unsigned int stacks = 10,
        float texU = 1.0f, float texV = 1.0f, const glm::vec4& color = { 1.0f, 1.0f, 1.0f, 1.0f });

    template<class VertexType , class IndexType = unsigned long>
    MeshData<VertexType, IndexType> CreateCylinderNoCap(float radius = 1.0f, float height = 2.0f, unsigned int slices = 20, unsigned int stacks = 10,
        float texU = 1.0f, float texV = 1.0f, const glm::vec4& color = { 1.0f, 1.0f, 1.0f, 1.0f });

    template<class VertexType , class IndexType = unsigned long>
    MeshData<VertexType, IndexType> CreateCone(float radius = 1.0f, float height = 2.0f, unsigned int slices = 20,
        const glm::vec4& color = { 1.0f, 1.0f, 1.0f, 1.0f });

    template<class VertexType , class IndexType = unsigned long>
    MeshData<VertexType, IndexType> CreateConeNoCap(float radius = 1.0f, float height = 2.0f, unsigned int slices = 20,
        const glm::vec4& color = { 1.0f, 1.0f, 1.0f, 1.0f });


    template<class VertexType , class IndexType = unsigned long>
    MeshData<VertexType, IndexType> Create2DShow(const glm::vec2& center, const glm::vec2& scale, const glm::vec4& color = { 1.0f, 1.0f, 1.0f, 1.0f });


    template<class VertexType , class IndexType = unsigned long>
    MeshData<VertexType, IndexType> Create2DShow(float centerX = 0.0f, float centerY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, const glm::vec4& color = { 1.0f, 1.0f, 1.0f, 1.0f });

    template<class VertexType , class IndexType = unsigned long>
    MeshData<VertexType, IndexType> CreateQuad(const glm::vec2& center, const glm::vec2& scale, const glm::vec4& color = { 1.0f, 1.0f, 1.0f, 1.0f });

    template<class VertexType , class IndexType = unsigned long>
    MeshData<VertexType, IndexType> CreateQuad(float centerX = 0.0f, float centerY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, const glm::vec4& color = { 1.0f, 1.0f, 1.0f, 1.0f });

    template<class VertexType , class IndexType = unsigned long>
    MeshData<VertexType, IndexType> CreatePlane(const glm::vec2& planeSize,
        const glm::vec2& maxTexCoord = { 1.0f, 1.0f }, const glm::vec4& color = { 1.0f, 1.0f, 1.0f, 1.0f });
    template<class VertexType , class IndexType = unsigned long>
    MeshData<VertexType, IndexType> CreatePlane(float width = 10.0f, float depth = 10.0f, float texU = 1.0f, float texV = 1.0f,
        const glm::vec4& color = { 1.0f, 1.0f, 1.0f, 1.0f });

    template<class VertexType , class IndexType = unsigned long>
    MeshData<VertexType, IndexType> CreateTerrain(const glm::vec2& terrainSize,
        const glm::ivec2& slices = { 10, 10 }, const glm::vec2& maxTexCoord = { 1.0f, 1.0f },
        const std::function<float(float, float)>& heightFunc = [](float x, float z) { return 0.0f; },
        const std::function<glm::vec3(float, float)>& normalFunc = [](float x, float z) { return glm::vec3(0.0f, 1.0f, 0.0f); },
        const std::function<glm::vec4(float, float)>& colorFunc = [](float x, float z) { return glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); });
    template<class VertexType , class IndexType = unsigned long>
    MeshData<VertexType, IndexType> CreateTerrain(float width = 10.0f, float depth = 10.0f,
        unsigned int slicesX = 10, unsigned int slicesZ = 10, float texU = 1.0f, float texV = 1.0f,
        const std::function<float(float, float)>& heightFunc = [](float x, float z) { return 0.0f; },
        const std::function<glm::vec3(float, float)>& normalFunc = [](float x, float z) { return glm::vec3(0.0f, 1.0f, 0.0f); },
        const std::function<glm::vec4(float, float)>& colorFunc = [](float x, float z) { return glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); });


}


namespace Geometry
{
    namespace Internal
    {
        struct VertexData
        {
            glm::vec3 pos;
            glm::vec3 normal;
            glm::vec4 tangent;
            glm::vec4 color;
            glm::vec2 tex;
        };

        template<class VertexType>
        inline void InsertVertexElement(VertexType& vertexDst, const VertexData& vertexSrc)
        {
            static std::string semanticName;
            static const std::map<std::string, std::pair<size_t, size_t>> semanticSizeMap = {
                {"POSITION", std::pair<size_t, size_t>(0, 12)},
                {"NORMAL", std::pair<size_t, size_t>(12, 24)},
                {"TANGENT", std::pair<size_t, size_t>(24, 40)},
                {"COLOR", std::pair<size_t, size_t>(40, 56)},
                {"TEXCOORD", std::pair<size_t, size_t>(56, 64)}
            };

            for (size_t i = 0; i < VertexType::inputLayout.size(); i++)
            {
                semanticName = VertexType::inputLayout[i].name;
                const auto& range = semanticSizeMap.at(semanticName);
                memcpy(reinterpret_cast<char*>(&vertexDst) + VertexType::inputLayout[i].offset,
                    reinterpret_cast<const char*>(&vertexSrc) + range.first,
                    range.second - range.first);
            }
        }
    }

    //

    template<class VertexType, class IndexType>
    inline MeshData<VertexType, IndexType> CreateSphere(float radius, unsigned int levels, unsigned int slices, const glm::vec4& color)
    {
        MeshData<VertexType, IndexType> meshData;
        unsigned int vertexCount = 2 + (levels - 1) * (slices + 1);
        unsigned int indexCount = 6 * (levels - 1) * slices;
        meshData.vertexVec.resize(vertexCount);
        meshData.indexVec.resize(indexCount);

        Internal::VertexData vertexData;
        IndexType vIndex = 0, iIndex = 0;

        float phi = 0.0f, theta = 0.0f;
        float per_phi = glm::pi<float>() / levels;
        float per_theta = glm::pi<float>() * 2.0f / slices;
        float x, y, z;

        vertexData = { glm::vec3(0.0f, radius, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.0f, 0.0f) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

        for (unsigned int i = 1; i < levels; ++i)
        {
            phi = per_phi * i;
            for (unsigned int j = 0; j <= slices; ++j)
            {
                theta = per_theta * j;
                x = radius * sinf(phi) * cosf(theta);
                y = radius * cosf(phi);
                z = radius * sinf(phi) * sinf(theta);

                glm::vec3 pos = glm::vec3(x, y, z), normal;
                //XMStoreFloat3(&normal, XMVector3Normalize(XMLoadFloat3(&pos)));
                normal = glm::normalize(pos);

                vertexData = { pos, normal, glm::vec4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, glm::vec2(theta / glm::pi<float>() * 2.0f, phi / glm::pi<float>()) };
                Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
            }
        }

        vertexData = { glm::vec3(0.0f, -radius, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f),
            glm::vec4(-1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.0f, 1.0f) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

        if (levels > 1)
        {
            for (unsigned int j = 1; j <= slices; ++j)
            {
                meshData.indexVec[iIndex++] = 0;
                meshData.indexVec[iIndex++] = j % (slices + 1) + 1;
                meshData.indexVec[iIndex++] = j;
            }
        }


        for (unsigned int i = 1; i < levels - 1; ++i)
        {
            for (unsigned int j = 1; j <= slices; ++j)
            {
                meshData.indexVec[iIndex++] = (i - 1) * (slices + 1) + j;
                meshData.indexVec[iIndex++] = (i - 1) * (slices + 1) + j % (slices + 1) + 1;
                meshData.indexVec[iIndex++] = i * (slices + 1) + j % (slices + 1) + 1;

                meshData.indexVec[iIndex++] = i * (slices + 1) + j % (slices + 1) + 1;
                meshData.indexVec[iIndex++] = i * (slices + 1) + j;
                meshData.indexVec[iIndex++] = (i - 1) * (slices + 1) + j;
            }
        }

        if (levels > 1)
        {
            for (unsigned int j = 1; j <= slices; ++j)
            {
                meshData.indexVec[iIndex++] = (levels - 2) * (slices + 1) + j;
                meshData.indexVec[iIndex++] = (levels - 2) * (slices + 1) + j % (slices + 1) + 1;
                meshData.indexVec[iIndex++] = (levels - 1) * (slices + 1) + 1;
            }
        }

        return meshData;
    }

    template<class VertexType, class IndexType>
    inline MeshData<VertexType, IndexType> CreateFrustum(Frustum f = {45.0f,1.0f,1.0f,50.0f})
    {
        using namespace glm;

        //w/h
        float nearZ = f.nNear;
        float farZ = f.nFar;

        float nearY = glm::tan(glm::radians(f.fovy / 2.0f)) * f.nNear;
        float nearX = nearY * f.aspect;

        float farY = glm::tan(glm::radians(f.fovy / 2.0f)) * f.nFar;
        float farX = farY * f.aspect;

        MeshData<VertexType, IndexType> meshData;
        meshData.vertexVec.resize(24);

        Internal::VertexData vertexDataArr[24];

        std::vector<glm::vec3> positions =
        {
            // near Plane
            glm::vec3(-nearX, -nearY, nearZ),
            glm::vec3( nearX, -nearY, nearZ),
            glm::vec3( nearX, nearY, nearZ),
            glm::vec3(-nearX, nearY, nearZ),

            // far Plane
            glm::vec3(-farX, -farY, farZ),
            glm::vec3(farX, -farY, farZ),
            glm::vec3(farX, farY, farZ),
            glm::vec3(-farX, farY, farZ),


        };
        vertexDataArr[0].pos = positions[1];
        vertexDataArr[1].pos = positions[2];
        vertexDataArr[2].pos = positions[6];
        vertexDataArr[3].pos = positions[5];
        vertexDataArr[4].pos = positions[0];
        vertexDataArr[5].pos = positions[3];
        vertexDataArr[6].pos = positions[7];
        vertexDataArr[7].pos = positions[4];
        vertexDataArr[16].pos = positions[4];
        vertexDataArr[17].pos = positions[5];
        vertexDataArr[18].pos = positions[6];
        vertexDataArr[19].pos = positions[7];
        vertexDataArr[20].pos = positions[0];
        vertexDataArr[21].pos = positions[1];
        vertexDataArr[22].pos = positions[2];
        vertexDataArr[23].pos = positions[3];

        for (unsigned int i = 0; i < 24; ++i)
        {
            Internal::InsertVertexElement(meshData.vertexVec[i], vertexDataArr[i]);
        }

        meshData.indexVec = {
             0, 1, 1, 2, 2, 3, 3, 0 ,
             4, 5, 5, 6, 6, 7, 7, 4 ,
             16, 17,17,18 ,18, 19,19,16 ,
             20, 21,21,22, 22, 23 ,23,20
        };


        return meshData;

        
    }

    template<class VertexType, class IndexType>
    inline MeshData<VertexType, IndexType> CreateBox(float width, float height, float depth, const glm::vec4& color)
    {
        using namespace glm;

        MeshData<VertexType, IndexType> meshData;
        meshData.vertexVec.resize(24);


        Internal::VertexData vertexDataArr[24];
        float w2 = width / 2, h2 = height / 2, d2 = depth / 2;

        vertexDataArr[0].pos = glm::vec3(w2, -h2, -d2);
        vertexDataArr[1].pos = glm::vec3(w2, h2, -d2);
        vertexDataArr[2].pos = glm::vec3(w2, h2, d2);
        vertexDataArr[3].pos = glm::vec3(w2, -h2, d2);
        vertexDataArr[4].pos = glm::vec3(-w2, -h2, d2);
        vertexDataArr[5].pos = glm::vec3(-w2, h2, d2);
        vertexDataArr[6].pos = glm::vec3(-w2, h2, -d2);
        vertexDataArr[7].pos = glm::vec3(-w2, -h2, -d2);

        vertexDataArr[8].pos = glm::vec3(-w2, h2, -d2);
        vertexDataArr[9].pos = glm::vec3(-w2, h2, d2);
        vertexDataArr[10].pos = glm::vec3(w2, h2, d2);
        vertexDataArr[11].pos = glm::vec3(w2, h2, -d2);
        vertexDataArr[12].pos = glm::vec3(w2, -h2, -d2);
        vertexDataArr[13].pos = glm::vec3(w2, -h2, d2);
        vertexDataArr[14].pos = glm::vec3(-w2, -h2, d2);
        vertexDataArr[15].pos = glm::vec3(-w2, -h2, -d2);
        vertexDataArr[16].pos = glm::vec3(w2, -h2, d2);
        vertexDataArr[17].pos = glm::vec3(w2, h2, d2);
        vertexDataArr[18].pos = glm::vec3(-w2, h2, d2);
        vertexDataArr[19].pos = glm::vec3(-w2, -h2, d2);
        vertexDataArr[20].pos = glm::vec3(-w2, -h2, -d2);
        vertexDataArr[21].pos = glm::vec3(-w2, h2, -d2);
        vertexDataArr[22].pos = glm::vec3(w2, h2, -d2);
        vertexDataArr[23].pos = glm::vec3(w2, -h2, -d2);

        for (unsigned int i = 0; i < 4; ++i)
        {
            vertexDataArr[i].normal = glm::vec3(1.0f, 0.0f, 0.0f);
            vertexDataArr[i].tangent = glm::vec4(0.0f, 0.0f, 1.0f, 1.0f);
            vertexDataArr[i].color = color;
            vertexDataArr[i + 4].normal = glm::vec3(-1.0f, 0.0f, 0.0f);
            vertexDataArr[i + 4].tangent = glm::vec4(0.0f, 0.0f, -1.0f, 1.0f);
            vertexDataArr[i + 4].color = color;
            vertexDataArr[i + 8].normal = glm::vec3(0.0f, 1.0f, 0.0f);
            vertexDataArr[i + 8].tangent = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f);
            vertexDataArr[i + 8].color = color;
            vertexDataArr[i + 12].normal = glm::vec3(0.0f, -1.0f, 0.0f);
            vertexDataArr[i + 12].tangent = glm::vec4(-1.0f, 0.0f, 0.0f, 1.0f);
            vertexDataArr[i + 12].color = color;
            vertexDataArr[i + 16].normal = glm::vec3(0.0f, 0.0f, 1.0f);
            vertexDataArr[i + 16].tangent = glm::vec4(-1.0f, 0.0f, 0.0f, 1.0f);
            vertexDataArr[i + 16].color = color;
            vertexDataArr[i + 20].normal = glm::vec3(0.0f, 0.0f, -1.0f);
            vertexDataArr[i + 20].tangent = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f);
            vertexDataArr[i + 20].color = color;
        }



        for (unsigned int i = 0; i < 6; ++i)
        {
            vertexDataArr[i * 4].tex = glm::vec2(0.0f, 1.0f);
            vertexDataArr[i * 4 + 1].tex = glm::vec2(0.0f, 0.0f);
            vertexDataArr[i * 4 + 2].tex = glm::vec2(1.0f, 0.0f);
            vertexDataArr[i * 4 + 3].tex = glm::vec2(1.0f, 1.0f);
        }


        for (unsigned int i = 0; i < 24; ++i)
        {
            Internal::InsertVertexElement(meshData.vertexVec[i], vertexDataArr[i]);
        }

        meshData.indexVec = {
            0, 1, 2, 2, 3, 0,		
            4, 5, 6, 6, 7, 4,		
            8, 9, 10, 10, 11, 8,	
            12, 13, 14, 14, 15, 12,	
            16, 17, 18, 18, 19, 16, 
            20, 21, 22, 22, 23, 20	
        };

        return meshData;
    }

    // Create Cube Rect
    template<class VertexType, class IndexType>
    MeshData<VertexType, IndexType> CreateBounding(float width, float height, float depth, const glm::vec4& color)
    {
        using namespace glm;

        MeshData<VertexType, IndexType> meshData;
        meshData.vertexVec.resize(24);


        Internal::VertexData vertexDataArr[24];
        float w2 = width / 2, h2 = height / 2, d2 = depth / 2;

        vertexDataArr[0].pos = glm::vec3(w2, -h2, -d2);
        vertexDataArr[1].pos = glm::vec3(w2, h2, -d2);
        vertexDataArr[2].pos = glm::vec3(w2, h2, d2);
        vertexDataArr[3].pos = glm::vec3(w2, -h2, d2);
        vertexDataArr[4].pos = glm::vec3(-w2, -h2, d2);
        vertexDataArr[5].pos = glm::vec3(-w2, h2, d2);
        vertexDataArr[6].pos = glm::vec3(-w2, h2, -d2);
        vertexDataArr[7].pos = glm::vec3(-w2, -h2, -d2);
        vertexDataArr[8].pos = glm::vec3(-w2, h2, -d2);
        vertexDataArr[9].pos = glm::vec3(-w2, h2, d2);
        vertexDataArr[10].pos = glm::vec3(w2, h2, d2);
        vertexDataArr[11].pos = glm::vec3(w2, h2, -d2);
        vertexDataArr[12].pos = glm::vec3(w2, -h2, -d2);
        vertexDataArr[13].pos = glm::vec3(w2, -h2, d2);
        vertexDataArr[14].pos = glm::vec3(-w2, -h2, d2);
        vertexDataArr[15].pos = glm::vec3(-w2, -h2, -d2);
        vertexDataArr[16].pos = glm::vec3(w2, -h2, d2);
        vertexDataArr[17].pos = glm::vec3(w2, h2, d2);
        vertexDataArr[18].pos = glm::vec3(-w2, h2, d2);
        vertexDataArr[19].pos = glm::vec3(-w2, -h2, d2);
        vertexDataArr[20].pos = glm::vec3(-w2, -h2, -d2);
        vertexDataArr[21].pos = glm::vec3(-w2, h2, -d2);
        vertexDataArr[22].pos = glm::vec3(w2, h2, -d2);
        vertexDataArr[23].pos = glm::vec3(w2, -h2, -d2);

        for (unsigned int i = 0; i < 24; ++i)
        {
            Internal::InsertVertexElement(meshData.vertexVec[i], vertexDataArr[i]);
        }

        meshData.indexVec = {
            0, 1, 1, 2, 2, 3, 3, 0 ,
            4, 5, 5, 6, 6, 7, 7, 4 ,
            //8, 9,9,10, 10, 11,11,8 ,
            //12, 13,13,14 ,14, 15,15,12 ,
            16, 17,17,18 ,18, 19,19,16 ,
            20, 21,21,22, 22, 23 ,23,20
        };

        return meshData;
    }

    template<class VertexType, class IndexType>
    inline MeshData<VertexType, IndexType> CreateCylinder(float radius, float height, unsigned int slices, unsigned int stacks,
        float texU, float texV, const glm::vec4& color)
    {
        using namespace glm;

        auto meshData = CreateCylinderNoCap<VertexType, IndexType>(radius, height, slices, stacks, texU, texV, color);
        unsigned int vertexCount = (slices + 1) * (stacks + 3) + 2;
        unsigned int indexCount = 6 * slices * (stacks + 1);
        meshData.vertexVec.resize(vertexCount);
        meshData.indexVec.resize(indexCount);

        float h2 = height / 2;
        float theta = 0.0f;
        float per_theta = glm::pi<float>() * 2.0f / slices;

        IndexType vIndex = (slices + 1) * (stacks + 1), iIndex = 6 * slices * stacks;
        IndexType offset = vIndex;
        Internal::VertexData vertexData;
        vertexData = { glm::vec3(0.0f, h2, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f),
            glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.5f, 0.5f) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

        for (unsigned int i = 0; i <= slices; ++i)
        {
            theta = i * per_theta;
            float u = cosf(theta) * radius / height + 0.5f;
            float v = sinf(theta) * radius / height + 0.5f;
            vertexData = { glm::vec3(radius * cosf(theta), h2, radius * sinf(theta)), glm::vec3(0.0f, 1.0f, 0.0f),
                glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(u, v) };
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
        }

        vertexData = { glm::vec3(0.0f, -h2, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f),
            glm::vec4(-1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.5f, 0.5f) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
        for (unsigned int i = 0; i <= slices; ++i)
        {
            theta = i * per_theta;
            float u = cosf(theta) * radius / height + 0.5f;
            float v = sinf(theta) * radius / height + 0.5f;
            vertexData = { glm::vec3(radius * cosf(theta), -h2, radius * sinf(theta)), glm::vec3(0.0f, -1.0f, 0.0f),
                glm::vec4(-1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(u, v) };
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
        }



        for (unsigned int i = 1; i <= slices; ++i)
        {
            meshData.indexVec[iIndex++] = offset;
            meshData.indexVec[iIndex++] = offset + i % (slices + 1) + 1;
            meshData.indexVec[iIndex++] = offset + i;
        }

        offset += slices + 2;
        for (unsigned int i = 1; i <= slices; ++i)
        {
            meshData.indexVec[iIndex++] = offset;
            meshData.indexVec[iIndex++] = offset + i;
            meshData.indexVec[iIndex++] = offset + i % (slices + 1) + 1;
        }

        return meshData;
    }

    template<class VertexType, class IndexType>
    inline MeshData<VertexType, IndexType> CreateCylinderNoCap(float radius, float height, unsigned int slices, unsigned int stacks,
        float texU, float texV, const glm::vec4& color)
    {
        using namespace glm;

        MeshData<VertexType, IndexType> meshData;
        unsigned int vertexCount = (slices + 1) * (stacks + 1);
        unsigned int indexCount = 6 * slices * stacks;
        meshData.vertexVec.resize(vertexCount);
        meshData.indexVec.resize(indexCount);

        float h2 = height / 2;
        float theta = 0.0f;
        float per_theta = glm::pi<float>() * 2.0f / slices;
        float stackHeight = height / stacks;

        Internal::VertexData vertexData;

        unsigned int vIndex = 0;
        for (unsigned int i = 0; i < stacks + 1; ++i)
        {
            float y = -h2 + i * stackHeight;
            for (unsigned int j = 0; j <= slices; ++j)
            {
                theta = j * per_theta;
                float u = theta / glm::pi<float>() * 2.0f;
                float v = 1.0f - (float)i / stacks;
                vertexData = { glm::vec3(radius * cosf(theta), y, radius * sinf(theta)), glm::vec3(cosf(theta), 0.0f, sinf(theta)),
                    glm::vec4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, glm::vec2(u * texU, v * texV) };
                Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
            }
        }

        unsigned int iIndex = 0;
        for (unsigned int i = 0; i < stacks; ++i)
        {
            for (unsigned int j = 0; j < slices; ++j)
            {
                meshData.indexVec[iIndex++] = i * (slices + 1) + j;
                meshData.indexVec[iIndex++] = (i + 1) * (slices + 1) + j;
                meshData.indexVec[iIndex++] = (i + 1) * (slices + 1) + j + 1;

                meshData.indexVec[iIndex++] = i * (slices + 1) + j;
                meshData.indexVec[iIndex++] = (i + 1) * (slices + 1) + j + 1;
                meshData.indexVec[iIndex++] = i * (slices + 1) + j + 1;
            }
        }



        return meshData;
    }

    template<class VertexType, class IndexType>
    MeshData<VertexType, IndexType> CreateCone(float radius, float height, unsigned int slices, const glm::vec4& color)
    {
        using namespace glm;
        auto meshData = CreateConeNoCap<VertexType, IndexType>(radius, height, slices, color);

        unsigned int vertexCount = 3 * slices + 1;
        unsigned int indexCount = 6 * slices;
        meshData.vertexVec.resize(vertexCount);
        meshData.indexVec.resize(indexCount);

        float h2 = height / 2;
        float theta = 0.0f;
        float per_theta = glm::pi<float>() * 2.0f / slices;
        unsigned int iIndex = 3 * slices;
        unsigned int vIndex = 2 * slices;
        Internal::VertexData vertexData;

        for (unsigned int i = 0; i < slices; ++i)
        {
            theta = i * per_theta;
            vertexData = { glm::vec3(radius * cosf(theta), -h2, radius * sinf(theta)), glm::vec3(0.0f, -1.0f, 0.0f),
                glm::vec4(-1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(cosf(theta) / 2 + 0.5f, sinf(theta) / 2 + 0.5f) };
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
        }
        vertexData = { glm::vec3(0.0f, -h2, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f),
                glm::vec4(-1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.5f, 0.5f) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

        unsigned int offset = 2 * slices;
        for (unsigned int i = 0; i < slices; ++i)
        {
            meshData.indexVec[iIndex++] = offset + slices;
            meshData.indexVec[iIndex++] = offset + i % slices;
            meshData.indexVec[iIndex++] = offset + (i + 1) % slices;
        }

        return meshData;
    }

    template<class VertexType, class IndexType>
    MeshData<VertexType, IndexType> CreateConeNoCap(float radius, float height, unsigned int slices, const glm::vec4& color)
    {
        using namespace glm;

        MeshData<VertexType, IndexType> meshData;
        unsigned int vertexCount = 2 * slices;
        unsigned int indexCount = 3 * slices;
        meshData.vertexVec.resize(vertexCount);
        meshData.indexVec.resize(indexCount);

        float h2 = height / 2;
        float theta = 0.0f;
        float per_theta = glm::pi<float>() * 2.0f / slices;
        float len = sqrtf(height * height + radius * radius);
        unsigned int iIndex = 0;
        unsigned int vIndex = 0;
        Internal::VertexData vertexData;

        for (unsigned int i = 0; i < slices; ++i)
        {
            theta = i * per_theta + per_theta / 2;
            vertexData = { glm::vec3(0.0f, h2, 0.0f), glm::vec3(radius * cosf(theta) / len, height / len, radius * sinf(theta) / len),
                glm::vec4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, glm::vec2(0.5f, 0.5f) };
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
        }

        for (unsigned int i = 0; i < slices; ++i)
        {
            theta = i * per_theta;
            vertexData = { glm::vec3(radius * cosf(theta), -h2, radius * sinf(theta)), glm::vec3(radius * cosf(theta) / len, height / len, radius * sinf(theta) / len),
                glm::vec4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, glm::vec2(cosf(theta) / 2 + 0.5f, sinf(theta) / 2 + 0.5f) };
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
        }

        for (unsigned int i = 0; i < slices; ++i)
        {
            meshData.indexVec[iIndex++] = i;
            meshData.indexVec[iIndex++] = slices + (i + 1) % slices;
            meshData.indexVec[iIndex++] = slices + i % slices;
        }

        return meshData;
    }

    template<class VertexType, class IndexType>
    inline MeshData<VertexType, IndexType> Create2DShow(const glm::vec2& center, const glm::vec2& scale, const glm::vec4& color)
    {
        return Create2DShow<VertexType, IndexType>(center.x, center.y, scale.x, scale.y, color);
    }

    template<class VertexType, class IndexType >
    MeshData<VertexType, IndexType> CreateQuad(const glm::vec2& center, const glm::vec2& scale, const glm::vec4& color)
    {
        return CreateQuad<VertexType, IndexType>(center.x, center.y, scale.x, scale.y, color);
    }
    template<class VertexType, class IndexType>
    inline MeshData<VertexType, IndexType> CreateQuad(float centerX, float centerY, float scaleX, float scaleY, const glm::vec4& color)
    {
        using namespace glm;

        MeshData<VertexType, IndexType> meshData;
        meshData.vertexVec.resize(4);

        Internal::VertexData vertexData;
        unsigned int vIndex = 0;

        vertexData = { glm::vec3(centerX - scaleX, centerY - scaleY, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f),
            glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.0f, 0.0f) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

        vertexData = { glm::vec3(centerX - scaleX, centerY + scaleY, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f),
            glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.0f, 1.0f) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

        vertexData = { glm::vec3(centerX + scaleX, centerY + scaleY, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f),
            glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(1.0f, 1.0f) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

        vertexData = { glm::vec3(centerX + scaleX, centerY - scaleY, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f),
            glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(1.0f, 0.0f) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

        meshData.indexVec = { 0, 1, 2, 2, 3, 0 };
        return meshData;
    }


  
    template<class VertexType, class IndexType>
    inline MeshData<VertexType, IndexType> Create2DShow(float centerX, float centerY, float scaleX, float scaleY, const glm::vec4& color)
    {
        using namespace glm;

        MeshData<VertexType, IndexType> meshData;
        meshData.vertexVec.resize(4);

        Internal::VertexData vertexData;
        unsigned int vIndex = 0;

        vertexData = { glm::vec3(centerX - scaleX, centerY - scaleY, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f),
            glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.0f, 1.0f) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

        vertexData = { glm::vec3(centerX - scaleX, centerY + scaleY, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f),
            glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.0f, 0.0f) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

        vertexData = { glm::vec3(centerX + scaleX, centerY + scaleY, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f),
            glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(1.0f, 0.0f) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

        vertexData = { glm::vec3(centerX + scaleX, centerY - scaleY, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f),
            glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(1.0f, 1.0f) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

        meshData.indexVec = { 0, 1, 2, 2, 3, 0 };
        return meshData;
    }
    template<class VertexType, class IndexType>
    inline MeshData<VertexType, IndexType> CreatePlane(const glm::vec2& planeSize,
        const glm::vec2& maxTexCoord, const glm::vec4& color)
    {
        return CreatePlane<VertexType, IndexType>(planeSize.x, planeSize.y, maxTexCoord.x, maxTexCoord.y, color);
    }

    template<class VertexType, class IndexType>
    inline MeshData<VertexType, IndexType> CreatePlane(float width, float depth, float texU, float texV, const glm::vec4& color)
    {
        using namespace glm;

        MeshData<VertexType, IndexType> meshData;
        meshData.vertexVec.resize(4);

        Internal::VertexData vertexData;
        unsigned int vIndex = 0;

        vertexData = { glm::vec3(-width / 2, 0.0f, -depth / 2), glm::vec3(0.0f, 1.0f, 0.0f),
            glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.0f, texV) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

        vertexData = { glm::vec3(-width / 2, 0.0f, depth / 2), glm::vec3(0.0f, 1.0f, 0.0f),
            glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.0f, 0.0f) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

        vertexData = { glm::vec3(width / 2, 0.0f, depth / 2), glm::vec3(0.0f, 1.0f, 0.0f),
            glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(texU, 0.0f) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

        vertexData = { glm::vec3(width / 2, 0.0f, -depth / 2), glm::vec3(0.0f, 1.0f, 0.0f),
            glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(texU, texV) };
        Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

        meshData.indexVec = { 0, 1, 2, 2, 3, 0 };
        return meshData;
    }

    template<class VertexType, class IndexType>
    MeshData<VertexType, IndexType> CreateTerrain(const glm::vec2& terrainSize, const glm::ivec2& slices,
        const glm::vec2& maxTexCoord, const std::function<float(float, float)>& heightFunc,
        const std::function<glm::vec3(float, float)>& normalFunc,
        const std::function<glm::vec4(float, float)>& colorFunc)
    {
        return CreateTerrain<VertexType, IndexType>(terrainSize.x, terrainSize.y, slices.x, slices.y,
            maxTexCoord.x, maxTexCoord.y, heightFunc, normalFunc, colorFunc);
    }

    template<class VertexType, class IndexType>
    MeshData<VertexType, IndexType> CreateTerrain(float width, float depth, unsigned int slicesX, unsigned int slicesZ,
        float texU, float texV, const std::function<float(float, float)>& heightFunc,
        const std::function<glm::vec3(float, float)>& normalFunc,
        const std::function<glm::vec4(float, float)>& colorFunc)
    {
        using namespace glm;

        MeshData<VertexType, IndexType> meshData;
        unsigned int vertexCount = (slicesX + 1) * (slicesZ + 1);
        unsigned int indexCount = 6 * slicesX * slicesZ;
        meshData.vertexVec.resize(vertexCount);
        meshData.indexVec.resize(indexCount);

        Internal::VertexData vertexData;
        unsigned int vIndex = 0;
        unsigned int iIndex = 0;

        float sliceWidth = width / slicesX;
        float sliceDepth = depth / slicesZ;
        float leftBottomX = -width / 2;
        float leftBottomZ = -depth / 2;
        float posX, posZ;
        float sliceTexWidth = texU / slicesX;
        float sliceTexDepth = texV / slicesZ;

        glm::vec3 normal;
        glm::vec4 tangent;
        // �������񶥵�
        //  __ __
        // | /| /|
        // |/_|/_|
        // | /| /| 
        // |/_|/_|
        for (unsigned int z = 0; z <= slicesZ; ++z)
        {
            posZ = leftBottomZ + z * sliceDepth;
            for (unsigned int x = 0; x <= slicesX; ++x)
            {
                posX = leftBottomX + x * sliceWidth;
                normal = normalFunc(posX, posZ);
                normal = glm::normalize(normal);
                tangent = glm::vec4(normal.y, -normal.x, 0.f, 1.0f) + glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);;

                vertexData = { glm::vec3(posX, heightFunc(posX, posZ), posZ),
                    normal, tangent, colorFunc(posX, posZ), glm::vec2(x * sliceTexWidth, texV - z * sliceTexDepth) };
                Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
            }
        }
        for (unsigned int i = 0; i < slicesZ; ++i)
        {
            for (unsigned int j = 0; j < slicesX; ++j)
            {
                meshData.indexVec[iIndex++] = i * (slicesX + 1) + j;
                meshData.indexVec[iIndex++] = (i + 1) * (slicesX + 1) + j;
                meshData.indexVec[iIndex++] = (i + 1) * (slicesX + 1) + j + 1;

                meshData.indexVec[iIndex++] = (i + 1) * (slicesX + 1) + j + 1;
                meshData.indexVec[iIndex++] = i * (slicesX + 1) + j + 1;
                meshData.indexVec[iIndex++] = i * (slicesX + 1) + j;
            }
        }

        return meshData;
    }


}
